# Copyright (c) HySoP 2011-2024
#
# This file is part of HySoP software.
# See "https://particle_methods.gricad-pages.univ-grenoble-alpes.fr/hysop-doc/"
# for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re, itertools as it
from hysop.tools.henum import EnumFactory
from hysop.tools.string_utils import prepend
from hysop.backend.hardware.hwinfo import TopologyObject, bytes2str
OperatingSystemDeviceType = EnumFactory.create(
"OperatingSystemDeviceType",
{
"BLOCK_DEVICE": 0x0, # Operating system block device.
# For instance "sda" on Linux.
"GPU_DEVICE": 0x1, # Operating system GPU device.
# For instance ":0.0" for a GL display, "card0" for a Linux DRM dev.
"NETWORK_DEVICE": 0x2, # Operating system network device.
# For instance the "eth0" interface on Linux.
"OPENFABRICS_DEVICE": 0x3, # Operating system openfabrics device.
# For instance the "mlx4_0" InfiniBand HCA device on Linux.
"DMA_DEVICE": 0x4, # Operating system dma engine device.
# For instance the "dma0chan0" DMA channel on Linux.
"COPROCESSOR_DEVICE": 0x5, # Operating system co-processor device.
# For instance "mic0" for a Xeon Phi (MIC) on Linux,
# "opencl0d0" for a OpenCL device, "cuda0" for a CUDA device.
},
)
"""
Type of an Operating System device (pci device function).
See hwloc documentation on type 'hwloc_obj_osdev_type_e'.
"""
[docs]
class OperatingSystemDevice(TopologyObject):
def __init__(self, parent, device):
super().__init__(parent, device)
def _parsed_type(self):
return "OSDev"
def _parse_object(self, it):
raise ValueError(f"Unknown object type {_type}.")
[docs]
def name(self):
return self.attribute("name")
[docs]
def osdev_type(self):
return self.attribute("osdev_type", 0, int)
# type 0 specific
[docs]
def type(self):
assert self.osdev_type() == 0
if "type" in self._attributes:
return self.attribute("type")
else:
return ""
[docs]
def model(self):
assert self.osdev_type() == 0
if "model" in self._attributes:
return self.attribute("model")
else:
return ""
[docs]
def serial_number(self):
assert self.osdev_type() == 0
return self.attribute("serial_number")
[docs]
def linux_device_id(self):
assert self.osdev_type() == 0
return self.attribute("linux_device_id")
# type 2 specific
[docs]
def address(self):
assert self.osdev_type() == 2
return self.attribute("address")
# type 6 specific
[docs]
def backend(self):
assert self.osdev_type() == 5
return self.attribute("backend")
[docs]
def backend_info(self):
assert self.osdev_type() == 5
if self.backend() == "CUDA":
self.print_attributes()
multi_processors = self.attribute("cuda_multi_processors", 0, int)
cores_per_mp = self.attribute("cuda_cores_per_mp", 0, int)
global_mem = (self.attribute("cuda_global_memory_size", (0,)),)
shared_mem_per_mp = (
self.attribute("cuda_shared_memory_size_per_mp", (0,)),
)
l2_cache_size = (self.attribute("cudal2_cache_size", (0,)),)
return {
"multi_processors": multi_processors,
"cores_per_mp": cores_per_mp,
"cores": cores_per_mp * multi_processors,
"global_memory_size": ", ".join(
tuple(
bytes2str(int(mem) * 1024 * 1024 // 1000) for mem in global_mem
)
),
"shared_memory_size_per_mp": ", ".join(
tuple(bytes2str(int(mem) * 1000) for mem in shared_mem_per_mp)
),
"l2_cache_size": ", ".join(
tuple(bytes2str(int(mem) * 1000) for mem in l2_cache_size)
),
}
else:
return None
def __str__(self):
_type = self.osdev_type()
osdev_type = OperatingSystemDeviceType[_type]
if osdev_type == OperatingSystemDeviceType.BLOCK_DEVICE:
return f"block_device: {self.type()} {self.model()} /dev/{self.name()}"
elif osdev_type == OperatingSystemDeviceType.GPU_DEVICE:
return f"gpu_device: {self.name()}"
elif osdev_type == OperatingSystemDeviceType.NETWORK_DEVICE:
return f"network_device: {self.name()} {self.address()}"
elif osdev_type == OperatingSystemDeviceType.OPENFABRICS_DEVICE:
return "openfabrics_device: {} node_guid={}".format(
self.name(), self.attribute("node_guid")
)
elif osdev_type == OperatingSystemDeviceType.DMA_DEVICE:
return "dma_device: PRINTING_NOT_IMPLEMENTED"
elif osdev_type == OperatingSystemDeviceType.COPROCESSOR_DEVICE:
header = f"coprocessor /dev/{self.name()} on backend {self.backend()}"
backend_info = self.backend_info()
if backend_info:
content = ""
for k, v in backend_info.items():
content += f"\n*{k}: {v}"
return header + self.indent(content)
else:
return "coprocessor_device: PRINTING_NOT_IMPLEMENTED"
else:
self.print_attributes()
raise ValueError(
"Unimplemented osdev printing for type {} ({}).".format(
osdev_type, _type
)
)
return content
[docs]
class PciDevice(TopologyObject):
def __init__(self, parent, device):
self._os_devices = []
super().__init__(parent, device)
[docs]
def leaf_pci_devices(self):
return [self]
def _post_init(self):
pci_type = self.pci_type()
regexp = r"([a-f0-9]{4})\s+\[([a-f0-9]{4}):([a-f0-9]{4})\]\s+"
regexp += r"\[([a-f0-9]{4}):([a-f0-9]{4})\]\s+([a-f0-9]{2})"
regexp = re.compile(regexp)
match = re.match(regexp, pci_type)
if not match:
msg = "Could not match pci device type '{}'."
msg = msg.format(pci_type)
raise ValueError(msg)
pci_device_class_id = match.group(1)
vendor_id = match.group(2)
device_id = match.group(3)
subvendor_id = match.group(4)
subdevice_id = match.group(5)
revision = match.group(6)
pci_device_class = self.pciids.find_device_class_by_id(pci_device_class_id)
vendor = self.pciids.find_vendor(vendor_id)
if not vendor:
vendor = vendor_id
device = None
else:
device = vendor.find_device(device_id, subdevice_id)
if not device:
device = f"[{vendor_id}:{device_id}]"
subvendor = self.pciids.find_vendor(subvendor_id)
if not subvendor:
subvendor = subvendor_id
subdevice = None
else:
subdevice = subvendor.find_device(subdevice_id)
if not subdevice:
subdevice = f"[{subvendor_id}:{subdevice_id}]"
self._attributes["pci_device_class_sid"] = pci_device_class_id
self._attributes["pci_system_vendor_sid"] = vendor_id
self._attributes["pci_system_device_sid"] = device_id
self._attributes["pci_subsystem_vendor_sid"] = subvendor_id
self._attributes["pci_subsystem_device_sid"] = subdevice_id
self._attributes["pci_device_revision_string"] = revision
self._attributes["pci_device_class_id"] = int(pci_device_class_id, 16)
self._attributes["pci_system_vendor_id"] = int(vendor_id, 16)
self._attributes["pci_system_device_id"] = int(device_id, 16)
self._attributes["pci_subsystem_vendor_id"] = int(subvendor_id, 16)
self._attributes["pci_subsystem_device_id"] = int(subdevice_id, 16)
self._attributes["pci_device_revision_value"] = int(revision, 16)
self._attributes["pci_device_class"] = pci_device_class
self._attributes["pci_system_vendor"] = vendor
self._attributes["pci_system_device"] = device
self._attributes["pci_subsystem_vendor"] = subvendor
self._attributes["pci_subsystem_device"] = subdevice
[docs]
def pci_link_speed(self):
return self.attribute("pci_link_speed")
[docs]
def pci_busid(self):
return self.attribute("pci_busid")
[docs]
def pci_type(self):
return self.attribute("pci_type")
[docs]
def vendor(self):
return self.pci_system_vendor()
[docs]
def device(self):
return self.pci_system_device()
[docs]
def vendor_id(self):
return self.pci_system_vendor_id()
[docs]
def device_id(self):
return self.pci_system_device_id()
[docs]
def subdevices(self):
return self.operating_system_devices()
[docs]
def subdevices_count(self):
return self.operating_system_devices_count()
[docs]
def pci_device_class_id(self):
return self.attribute("pci_device_class_id")
[docs]
def pci_device_class_sid(self):
return self.attribute("pci_device_class_sid")
[docs]
def pci_device_class(self):
return self.attribute("pci_device_class")
[docs]
def pci_device_revision_val(self):
return self.attribute("pci_device_revision_value")
[docs]
def pci_device_revision_str(self):
return self.attribute("pci_device_revision_string")
[docs]
def pci_system_vendor_id(self):
return self.attribute("pci_system_vendor_id")
[docs]
def pci_system_vendor_sid(self):
return self.attribute("pci_system_vendor_sid")
[docs]
def pci_system_vendor(self):
return self.attribute("pci_system_vendor")
[docs]
def pci_system_device_id(self):
return self.attribute("pci_system_device_id")
[docs]
def pci_system_device_sid(self):
return self.attribute("pci_system_device_sid")
[docs]
def pci_system_device(self):
return self.attribute("pci_system_device")
[docs]
def pci_subsystem_vendor_id(self):
return self.attribute("pci_subsystem_vendor_id")
[docs]
def pci_subsystem_vendor_sid(self):
return self.attribute("pci_subsystem_vendor_sid")
[docs]
def pci_subsystem_vendor(self):
return self.attribute("pci_subsystem_vendor")
[docs]
def pci_subsystem_device_id(self):
return self.attribute("pci_subsystem_device_id")
[docs]
def pci_subsystem_device_sid(self):
return self.attribute("pci_subsystem_device_sid")
[docs]
def pci_subsystem_device(self):
return self.attribute("pci_subsystem_device")
[docs]
def operating_system_devices_count(self):
return len(self._os_devices)
[docs]
def operating_system_devices(self):
return self._os_devices
[docs]
def to_string(self, expand_pci_tree=True, **kargs):
if expand_pci_tree:
header = f"{self.pci_busid()} {self.pci_device_class()}"
content = f"\nvendor: {self.pci_system_vendor()}"
content += f"\ndevice: {self.pci_system_device()}"
if self.pci_system_vendor_id() != self.pci_subsystem_vendor_id():
content += f"\nsubvendor: {self.pci_subsystem_vendor()}"
content += f"\nsubdevice: {self.pci_subsystem_device()}"
content += f"\nrevision: 0x{self.pci_device_revision_str()}"
if self.subdevices_count() > 0:
subcontent = ""
for osdev in self.subdevices():
subcontent += f"\n> {osdev}"
content += prepend(subcontent, 2 * " ")
content += "\n"
return header + prepend(content, 5 * " ")
else:
return "{} {} ({})".format(
self.pci_busid(), self.device(), self.pci_device_class().name
)
def __str__(self):
return self.to_string()
def _parsed_type(self):
return "PCIDev"
def _parse_object(self, it):
_type = it.attrib["type"]
if _type == "OSDev":
obj = OperatingSystemDevice(self, it)
self._os_devices.append(obj)
else:
raise ValueError(f"Unknown object type {_type}.")
[docs]
class PciBridge(TopologyObject):
def __init__(self, parent, bridge):
self._pci_devices = []
super().__init__(parent, bridge)
[docs]
def pci_devices(self, split=False):
devs = tuple(sorted(self._pci_devices, key=lambda x: x.os_index()))
if split:
devices = [dev for dev in devs if isinstance(dev, PciBridge)]
devices += [dev for dev in devs if isinstance(dev, PciDevice)]
return devices
else:
return devs
[docs]
def pci_devices_count(self):
return len(self._pci_devices)
[docs]
def leaf_pci_devices(self):
return it.chain.from_iterable([x.leaf_pci_devices() for x in self._pci_devices])
[docs]
def bridge_pci(self):
return self.attribute("bridge_pci")
[docs]
def bridge_type(self):
return self.attribute("bridge_type")
[docs]
def bridge_depth(self):
return self.attribute("depth", 0, int)
def __str__(self):
return self.to_string()
[docs]
def to_string(self, expand_pci_tree=True, is_last=False):
header = f"Bridge {self.bridge_pci()}"
content = ""
devices = self.pci_devices(split=True)
is_root = self.bridge_depth() == 0
if is_root:
prefix = "x-"
else:
prefix = ""
if (not is_root) and (is_last):
extra_pad = 3
else:
extra_pad = 0
if expand_pci_tree and self.pci_devices_count() == 1:
extra_bar = "|\n"
else:
extra_bar = ""
for dev_id, pci_device in enumerate(devices[:-1]):
pci_device = pci_device.to_string(expand_pci_tree)
pci_device = pci_device.split("\n")
pci_device[0] = "|--" + pci_device[0]
for i in range(1, len(pci_device)):
pci_device[i] = "| " + pci_device[i]
if (dev_id == 0) and expand_pci_tree:
pci_device = ["|"] + pci_device
pci_device = "\n".join(pci_device)
branch = self.indent(pci_device, len(prefix))
content += "\n" + branch
pci_device = devices[-1].to_string(expand_pci_tree, is_last=True)
branch = self.indent(f"{extra_bar}|__{pci_device}", extra_pad + len(prefix))
content += "\n" + branch
return prefix + header + content
def _parsed_type(self):
return "Bridge"
def _parse_object(self, it):
_type = it.attrib["type"]
if _type == "PCIDev":
obj = PciDevice(self, it)
self._pci_devices.append(obj)
elif _type == "Bridge":
obj = PciBridge(self, it)
self._pci_devices.append(obj)
else:
raise ValueError(f"Unknown object type {_type}.")